package fr.babos.navigation.routes 
{
	import fr.babos.core.data.items.IDataItem;
	import fr.babos.core.errors.BabosError;
	import fr.babos.utils.SlugUtils;
	
	/**
	 * @author ZoulouX
	 */
	public class RouteItem implements IDataItem
	{
		/**
		 * Les délimiteurs pour parser les URL
		 */
		public static var LEFT_DELIMITER			:String = "{";
		public static var RIGHT_DELIMITER			:String = "}";
		public static var RULE_DELIMITER			:String = ":";
		
		/**
		 * Les regex pour les caractères
		 */
		public static var NUM_PATTERN				:String = "([0-9]+)";
		public static var REQ_NUM_PATTERN			:String = "([0-9]*)";
		public static var ALPHA_PATTERN				:String = "([a-zA-Z]+)";
		public static var REQ_ALPHA_PATTERN			:String = "([a-zA-Z]*)";
		public static var ALPHANUM_PATTERN			:String = "([a-zA-Z0-9-_]+)";
		public static var REQ_ALPHANUM_PATTERN		:String = "([a-zA-Z0-9-_]*)";
		
		
		/**
		 * La regex de la route faisant office de signature
		 */
		protected var _signature					:RegExp;
		
		/**
		 * Le pattern de la route
		 */
		protected var _pattern						:String;
		
		/**
		 * La commande associée
		 */
		protected var _command						:String;
		
		/**
		 * Les paramètres de la route (juste leur noms)
		 */
		protected var _params						:Array;
		
		/**
		 * Les types de paramètres
		 */
		protected var _paramTypes					:Array;
		
		
		/**
		 * La signature de la route
		 */
		public function get signature ():RegExp { return _signature };
		
		/**
		 * Le pattern de la route
		 */
		public function get pattern ():String { return _pattern; }
		public function set pattern (pValue:String):void
		{
			// Enregistrer le pattern
			_pattern = pValue;
			
			// Parser le pattern
			parsePattern();
		}
		
		/**
		 * La commande associée
		 */
		public function get command ():String { return _command; }
		public function set command (pValue:String):void
		{
			_command = pValue;
		}
		
		/**
		 * Les paramètres de la route
		 */
		public function get params ():Array { return _params; }
		
		/**
		 * Les types des paramètres de la route
		 */
		public function get paramTypes ():Array { return _paramTypes; }
		
		
		/**
		 * Le constructeur de la route. Les parmaètres sont associés à la construction.
		 * @param	pCommand : La commande associée à la route
		 * @param	pPattern : Le pattern de la route
		 */
		public function RouteItem (pCommand:String = "", pPattern:String = ""):void
		{
			// Enregistrer les paramètres
			_command = pCommand;
			
			// Si on a un pattern de défini
			if (pPattern != "")
				pattern = pPattern;
		}
		
		/**
		 * Parser le pattern
		 */
		protected function parsePattern ():void
		{
			// Vider les noms de paramètres et leurs types
			_params = [];
			_paramTypes = [];
			
			// Copier le pattern
			var pattern:String = _pattern;
			
			// La signature que l'on va créer
			var signature:String = "";
			
			// L'index de la variable
			var varIndex:uint = 0;
			
			// La règle appliquée au paramètre
			var rule:String;
			
			// Nom de la règle
			var ruleName:String;
			
			// Variable de swap pour les délimiteur de paramètres
			var splited:Array;
			
			// Tant qu'il y a des variables dans l'URL
			while (pattern.indexOf(LEFT_DELIMITER) != -1 && pattern.indexOf(RIGHT_DELIMITER) != -1)
			{
				// Récupérer le début la fin du nom de la variable
				var index1:int = pattern.indexOf(LEFT_DELIMITER);
				var index2:int = pattern.indexOf(RIGHT_DELIMITER);
				
				// Récupérer son nom
				var varName:String = pattern.slice(index1 + 1, index2);
				
				// Vérifier si on a une séparateur
				if (varName.indexOf(RULE_DELIMITER) != -1)
				{
					// Séparer avant et après le séparateur
					splited = varName.split(RULE_DELIMITER, 2);
					
					// Récupérer le varName
					varName = splited[0];
					
					// Récupérer le nom de la règle
					ruleName = splited[1];
					
					// Récupérer la règle selon son nom
					if (RouteItem[ruleName.toUpperCase() + "_PATTERN"] != null)
					{
						// Une règle nommée existe
						rule = RouteItem[ruleName.toUpperCase() + "_PATTERN"];
					}
					else
					{
						// Règle personnalisée
						rule = ruleName;
					}
				}
				else
				{
					// Règle par défaut
					rule = ALPHANUM_PATTERN;
				}
				
				// Enregistrer le nom de cette variables
				_params[varIndex] = varName;
				
				// Enregistrer son type
				_paramTypes[varIndex] = rule;
				
				// Ajouter le premier bout ainsi que l'url
				signature += SlugUtils.cleanRule(pattern.slice(0, index1)) + rule;
				
				// Retailler l'url après le paramètre que l'on vient de parser
				pattern = pattern.slice(index2 + 1, pattern.length);
				
				// Ajouter la fin de l'url
				if (pattern.indexOf(RIGHT_DELIMITER) == -1)
					signature += SlugUtils.cleanRule(pattern);
				
				// Incrémenter l'index
				varIndex ++;
			}
			
			// Si on a pas de paramètres, on clean
			if (signature == "")
				signature = SlugUtils.cleanRule(_pattern);
			
			// Enregistrer la signature en Regex
			_signature = new RegExp("^" + signature + "$", "i");
		}
		
		/**
		 * Vérifier si cette route correspond à une commande et une liste de paramètres.
		 * @param	pCommand : La commande à vérifier
		 * @param	pParams : Les paramètres nommés de la commande (le nom et le type sera vérifié)
		 * @return : Retourne true si cette route peut être pratiquée
		 */
		public function checkCommand (pCommand:String, pParams:Object = null):Boolean
		{
			// Initialiser les paramètres s'il ne sont pas spécifiés
			if (pParams == null)
				pParams = {};
			
			// Vérifier le nom de la commande
			// Et le nombre de paramètres
			if (pCommand == _command)
			{
				// Le nombre 
				var total:uint = 0;
				var i:uint = 0;
				
				// Compter le nombre d'éléments passés dans les params
				for (var z:* in pParams)
				{
					total ++;
				}
				
				// Parcourir les paramètres de la route
				for (i = 0; i < _params.length; i++)
				{
					// Vérifier chaqu'un des params qu'on nous a donné, tester aussi le type.
					if (pParams[_params[i]] == null || !(new RegExp("^" + _paramTypes[i] + "$", "i").test(pParams[_params[i]])))
					{
						// S'il y en a un qui cloche, on coupe.
						return false;
					}
				}
				
				// Si le nombre de paramètres correspond, alors tout est ok
				return total == i;
			}
			else
			{
				// La signature ne correspond pas
				return false;
			}
		}
		
		/**
		 * Vérifier si cette route correspond à une URL donnée.
		 * @param	pURL : L'URL
		 * @return : Retourne si la route correspond
		 */
		public function checkURL (pURL:String):Boolean
		{
			// Vérifier / retourner
			return _signature.test(pURL);
		}
		
		/**
		 * Récupérer les paramètres depuis une URL vérifiée.
		 * @param	pURL : L'URL
		 * @return : Retourne la liste des paramètres, décodés depuis l'URL.
		 */
		public function getParamsFromURL (pURL:String):Object
		{
			// La liste des paramètres
			var params:Object = { };
			
			// Vérifier l'URL
			if (checkURL(pURL))
			{
				// Récupérer les variables depuis la signature
				var search:Object = _signature.exec(pURL);
				
				// Parser le résultat de recherche
				for (var i:* in search)
				{
					// Si cette élément de recherche est un paramètres
					if (i != "index" && i != "input" && i != 0)
					{
						// Enregistrer le paramètres
						params[_params[i - 1]] = search[i];
					}
				}
			}
			
			// Retourner les paramètres parsés
			return params;
		}
		
		/**
		 * Créer la route inverse depuis les paramètres. Utiliser checkCommand avant pour éviter les erreurs.
		 * @param	pParams : La liste des paramètres, peut être null si pas de paramètres sur cette route.
		 * @return : L'URL de la route avec ces paramètres
		 */
		public function getURLFromParams (pParams:Object):String
		{
			// Dupliquer le pattern pour le traitement
			var pattern	:String		= _pattern;
			
			// La nouvelle URL pour la sortie
			var newURL	:String 	= "";
			
			// Les index à chercher
			var index1	:int;
			var index2	:int;
			
			// Le nom du paramètres
			var varName	:String;
			
			// Tant qu'il y a des variables dans l'URL
			while (pattern.indexOf(LEFT_DELIMITER) != -1 && pattern.indexOf(RIGHT_DELIMITER) != -1)
			{
				// Récupérer le début la fin du nom de la variable
				index1 = pattern.indexOf(LEFT_DELIMITER);
				index2 = pattern.indexOf(RIGHT_DELIMITER);
				
				// Récupérer son nom
				varName = pattern.slice(index1 + 1, index2);
				
				// Découper les :
				if (varName.indexOf(RULE_DELIMITER) != -1)
					varName = varName.slice(0, varName.indexOf(RULE_DELIMITER));
				
				// Vérifier si on a ce paramètres
				if (pParams[varName] != null)
				{
					// Ajouter le premier bout ainsi que le pattern
					newURL += pattern.slice(0, index1) + SlugUtils.slugify(pParams[varName]);
				}
				else
				{
					// Erreur, le paramètres de ce nom n'est pas dispo dans le paramètre pParams
					//throw new BabosError("RouteItem.getURLFromParams", "A param is missing, check before convert.");
					
				}
				
				// Retailler le pattern après le paramètre que l'on vient de parser
				pattern = pattern.slice(index2 + 1, pattern.length);
				
				// Ajouter la fin de l'url
				if (pattern.indexOf(RIGHT_DELIMITER) == -1)
					newURL += pattern;
			}
			
			// Retourner la nouvelle URL
			return newURL == "" ? pattern : newURL;
		}
		
		/**
		 * Afficher cette route
		 */
		public function toString ():String
		{
			return "RouteItem{" + _signature.source + ", " + _pattern + ", (" + _pattern.concat(", ") + ")}";
		}
	}
}